import { statusBarHeight } from "./helper";
import { ROOT, getDeviceUuid, httpPost, md5, tryRun } from "./utils";

interface Position {
    x: number;
    y: number;
}

export class Ast {
    static barHeight: number = statusBarHeight();
    packageName: string;
    id: string = ``;
    className: string;
    text: string;
    desc: string;
    visibleToUser: boolean;
    bounds: AutoJs.Rect;
    clickable: boolean;
    checked: boolean;
    editable: boolean;
    focusable: boolean;
    longClickable: boolean;
    scrollable: boolean;
    selected: boolean;
    contextClickable: boolean;
    drawingOrder: number;
    depth: number;
    indexInParent: number;
    children: Ast[] = [];
    parent: Ast;
    width: number;
    height: number;
    maxDepth() {
        if (this.children && this.children.length > 0) {
            return this.children.map(child => {
                const maxDepth = child.maxDepth()
                return maxDepth;
            }).reduce((a, b) => a > b ? a : b, 0)
        }
        return this.index;
    }
    textTexts(): string {
        return [this.text, ...this.children.map(c => c.textTexts())].filter(it => !!it).join(' ')
    }
    sid: string = ``
    constructor(public node: AutoJs.UiObject | null, public index: number) {
        try {
            if (node) {
                this.onInit(node)
            }
        } catch (e) {
            console.error(`new ast on init error`)
        }
    }
    onInit(node: AutoJs.UiObject) {
        if (node) {
            this.packageName = node?.packageName() || ``
            // com.smile.gifmaker:id/live_red_packet_container_close_view
            this.id = node?.id() || ``;
            if (this.id) {
                const [prev, id] = this.id.split('/')
                this.sid = id || ``;
            }
            this.className = node?.className() || ``
            this.text = node?.text();
            this.desc = node?.desc() || ''
            this.visibleToUser = node?.visibleToUser()
            this.bounds = node?.bounds()
            this.width = this.bounds?.right - this.bounds?.left;
            this.height = this.bounds?.bottom - this.bounds?.top;
            this.clickable = node?.clickable()
            this.checked = node?.checked()
            this.editable = node?.editable;
            this.focusable = node?.focusable()
            this.longClickable = node?.longClickable()
            this.scrollable = node?.scrollable()
            this.selected = node?.selected()
            this.contextClickable = node?.contextClickable;
            this.drawingOrder = node?.drawingOrder();
            this.depth = node?.depth()
            this.indexInParent = node?.indexInParent()
            this._createChildren()
        } else {
            this.visibleToUser = false;
        }
    }
    nextSibling() {
        return this.parent.children[this.indexInParent + 1]
    }
    prevSibling() {
        return this.parent.children[this.indexInParent - 1]
    }
    visit<C>(visitor: Visitor<C>, ctx: C): C {
        return visitor.visitNode(this, ctx)
    }
    toString(onlyVisiable: boolean = false) {
        if (!onlyVisiable) return `${this._dent()}<${this.className} ${this.render()}>${this.children.map(child => child.toString()).join('')}${this._dent()}</${this.className}>`
        if (this.visibleToUser) {
            return `${this._dent()}<${this.className} ${this.render()}>${this.children.filter(it => it.visibleToUser).map(child => child.toString(onlyVisiable)).join('')}${this._dent()}</${this.className}>`
        }
        return ``
    }
    hasChildren() {
        return this.children.length > 0
    }

    inScreen() {
        if(!this.bounds) return;
        const { left, top, right, bottom } = this.bounds;
        if (this.checkIsInReact({ x: left, y: top })) return true;
        if (this.checkIsInReact({ x: right, y: bottom })) return true;
        const width = device.width;
        const height = device.height - Ast.barHeight;
        // x包含屏幕
        const xContainerScreen = left < 0 && right > width;
        const xInScreen = left > 0 && left < right;
        // y包含屏幕
        const yContainerScreen = top < 0 && bottom > height;
        const yInScreen = top > 0 && top < height;
        // x包含屏幕 y在屏幕内
        if (xContainerScreen && yInScreen) return true;
        if (yContainerScreen && xInScreen) return true;
        if (xContainerScreen && yContainerScreen) return true;
        return false;
    }
    checkIsInReact(
        { x, y }: Position,
        topLeft: Position = { x: 0, y: Ast.barHeight },
        bottomRight: Position = {
            x: device.width,
            y: device.height - Ast.barHeight,
        },
    ) {
        return (
            y < bottomRight.y && y > topLeft.y && x > topLeft.x && x < bottomRight.x
        );
    }

    toFile(name: string) {
        const saveDir = files.join(ROOT, `layout`)
        files.write(files.join(saveDir, name + `.xml`), this.toString(), 'utf-8')
    }
    autoSave(activity: string) {
        const content = this.toString()
        const name = md5(content)
        const dirname = files.join(ROOT, `layout/${this.packageName}/${activity}/${name}.html`);
        files.ensureDir(dirname);
        files.write(dirname, content, 'utf-8')
    }
    click() {
        if (this.clickable) tryRun(() => this.node.click())
    }
    clickRect(offsetX: number = 0, offsetY: number = 0) {
        click(this.bounds.centerX() + offsetX, this.bounds.centerY() + offsetY)
    }
    toSecond() {
        const statusText = this.text;
        if (!statusText) return 0;
        const timeReg = /(\d\d)\:(\d\d)/
        const res = statusText.match(timeReg)
        if (res && res.length > 1) {
            const [, m1, s1] = res;
            const second = parseInt(m1) * 60 + parseInt(s1)
            return second;
        }
        return 0;
    }
    formatDate(date: Date) {
        const year = date.getFullYear();
        const month = ('0' + (date.getMonth() + 1)).slice(-2);
        const day = ('0' + date.getDate()).slice(-2);
        const hour = ('0' + date.getHours()).slice(-2);
        const minute = ('0' + date.getMinutes()).slice(-2);
        const second = ('0' + date.getSeconds()).slice(-2);
        return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
    }
    second(s: number) {
        return new Date(new Date().getTime() + s * 1000)
    }
    scrollUp() {
        if (this.scrollable) {
            return this.node?.scrollUp()
        }
        return false;
    }
    scrollDown() {
        if (this.scrollable) {
            return this.node?.scrollDown()
        }
        return false;
    }
    scrollLeft() {
        if (this.scrollable) {
            return this.node?.scrollLeft()
        }
        return false;
    }
    scrollRight() {
        if (this.scrollable) {
            return this.node?.scrollRight()
        }
        return false;
    }
    scrollBackward() {
        if (this.scrollable) {
            return this.node.scrollBackward()
        }
        return false;
    }
    scrollForward() {
        if (this.scrollable) {
            return this.node.scrollForward()
        }
        return false;
    }
    private render() {
        return [this.renderId(), this.renderInScreen(), this.renderSize(), this.renderPosition(), this.renderSelected(), this.renderLongClickable(), this.renderChecked(), this.renderText(), this.renderDesc(), this.renderClickable(), this.renderVisibleToUser(), this.renderScrollable()].filter(it => !!it).join(' ')
    }
    private renderInScreen() {
        if (this.inScreen()) return `inScreen="true"`
    }
    private renderSize() {
        return `size="${this.width} ${this.height}"`
    }
    private renderPosition() {
        if (this.bounds) {
            const { left, top, right, bottom } = this.bounds;
            return `position="${left} ${top} ${right} ${bottom}"`
        }
        return ``
    }
    private renderSelected() {
        if (this.selected) return `selected="true"`
        return ``
    }
    private renderLongClickable() {
        if (this.longClickable) return `longClickable="true"`
        return ``
    }
    private renderChecked() {
        if (this.checked) return `checked="true"`
        return ``
    }
    private renderScrollable() {
        if (this.scrollable) return `scrollable="true"`
        return ``
    }
    private renderId() {
        if (this.id) return `id="${this.id}"`
        return '';
    }
    private renderText() {
        if (this.text) return `text="${this.text}"`
        return '';
    }
    private renderDesc() {
        if (this.desc) return `desc="${this.desc}"`
        return '';
    }
    private renderClickable() {
        if (this.clickable) return `clickable="${this.clickable}"`
        return '';
    }
    private renderVisibleToUser() {
        if (this.visibleToUser) return `visible="${this.visibleToUser}"`
        return '';
    }
    private _dent() {
        return '\n' + this._dentTab()
    }
    private _dentTab() {
        return new Array(this.index).fill('\t').join('')
    }
    private _createChildren() {
        tryRun(() => {
            this.node?.children().each(e => {
                const ast = new Ast(e, this.index + 1)
                ast.parent = this;
                this.children.push(ast);
            })
        })
    }
    findInParent(filter: (c: Ast) => boolean): Ast | undefined {
        if (!this.parent) return;
        if (this.parent && filter(this.parent)) return this.parent;
        return this.parent.findInParent(filter)
    }
    findOneInParent(filter: (c: Ast) => boolean) {
        if (!this.parent) return;
        if (this.parent && filter(this.parent)) return this.parent;
        const node = this.parent.findOne(filter)
        if (node) return node;
        return this.parent.findOneInParent(filter)
    }
    root(): Ast {
        if (this.parent) return this.parent.root()
        return this;
    }
    findOne(filter: (c: Ast) => boolean): Ast | undefined {
        let target: Ast;
        // 先看第一级
        for (let child of this.children) {
            if (target) {
                break;
            }
            if (filter(child)) {
                target = child;
                break;
            }
        }
        if (target) return target;
        for (let child of this.children) {
            target = child.findOne(filter)
            if (target) break;
        }
        return target;
    }
}

export class RootAst extends Ast {
    constructor(node: AutoJs.UiObject | null) {
        super(node, 0)
    }
    getId() {
        if (!this.node) return ``;
        if (this.node.id) return this.node.id;
        const ids = this.children.filter(it => !!it.id).map(it => it.id)
        if (ids.length > 0) return ids[0];
        return ``
    }
}

export interface Visitor<C = any, T = any> {
    visitNode(node: Ast, ctx: C): T;
}


export function createRootAst(): Ast {
    try {
        const node = selector().findOne()
        if (!node) {
            return new RootAst(null);
        }
        return new RootAst(node)
    } catch (e) {
        return new RootAst(null);
    }
}

export function uploadXml(title: string, name: string) {
    tryRun(() => {
        const root = createRootAst()
        if (!root) return;
        httpPost('addPage', {
            packageName: root.packageName,
            activityName: currentActivity(),
            content: root.toString(false),
            deviceUuid: getDeviceUuid(),
            title: title,
            name: name
        });
    }, e => toast(`【${title}.${name}】root xml upload error: ` + e.message))
}

export class MultiVisitor<C> implements Visitor<C> {
    stop: (ctx: C) => boolean;
    constructor(public visitors: Visitor[]) { }

    setStop(stop: (ctx: C) => boolean) {
        this.stop = stop
    }
    visitNode(node: Ast, ctx: C): void {
        this.visitors.map(visitor => {
            if (this.stop && this.stop(ctx)) return;
            return visitor.visitNode(node, ctx)
        })
    }
}
